package com.issac.messagequeue.mqserver.datacenter;

import com.issac.messagequeue.mqserver.common.BinaryTool;
import com.issac.messagequeue.mqserver.common.MqException;
import com.issac.messagequeue.mqserver.coreclass.MSGQueue;
import com.issac.messagequeue.mqserver.coreclass.Message;
import jdk.jfr.FlightRecorder;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.util.LinkedList;
import java.util.Scanner;

/**
 * MessageFileManager：管理"硬盘"上的消息
 */
@Slf4j
public class MessageFileManager {
    public void init() {
        // TODO: 暂无实现，后续可扩展
    }

    // 获取消息文件所在目录&文件名
    private String getQueueDir(String queueName) {
        return "./data/" + queueName;
    }

    // 获取指定队列的消息数据文件路径
    private String getQueueDataPath(String queueName) {
        return getQueueDir(queueName) + "/queue_data.txt";
    }

    // 获取指定队列的消息统计文件路径
    private String getQueueStatPath(String queueName) {
        return getQueueDir(queueName) + "/queue_stat.txt";
    }

    static public class Stat {
        public int totalCount;  // 该队列消息总数
        public int validCount;  // 该队列有效消息数
    }

    // 读取统计文件
    private Stat readStat(String queueName) {
        // queue_stat.txt 是"文本文件"
        Stat stat = new Stat();
        try (InputStream inputStream = new FileInputStream(getQueueStatPath(queueName))) {
            Scanner scanner = new Scanner(inputStream);
            log.info("[MessageFileManager] 正在读取消息统计文件！");
            stat.totalCount = scanner.nextInt();
            stat.validCount = scanner.nextInt();
            log.info("[MessageFileManager] 消息统计文件读取完毕！");
            return stat;
        } catch (IOException e) {
            log.warn("[MessageFileManager] 无法读取消息统计文件: {}", e.getMessage());
            e.printStackTrace();
        }
        return null;
    }

    // 写入统计文件
    private void writeStat(String queueName, Stat stat) {
        try (OutputStream outputStream = new FileOutputStream(getQueueStatPath(queueName))) {
            PrintWriter printWriter = new PrintWriter(outputStream);
            log.info("[MessageFileManager] 正在写入消息统计文件！");
            printWriter.write(stat.totalCount + "\t" + stat.validCount);
            printWriter.flush();
            log.info("[MessageFileManager] 消息统计文件写入完毕！");
        } catch (IOException e) {
            log.warn("[MessageFileManager] 无法写入消息统计文件：{}", e.getMessage());
            e.printStackTrace();
        }
    }

    // 创建队列对应文件&目录
    public void createQueueFiles(String queueName) throws IOException {
        // 1. 创建队列存储目录
        File baseDir = new File(getQueueDir(queueName));
        if (!baseDir.exists()) {
            boolean createOK = baseDir.mkdirs();
            if (!createOK) {
                log.error("[MessageFileManager] 创建目录失败: {}", baseDir.getAbsolutePath());
                throw new IOException("[MessageFileManager] 创建目录失败！ baseDir=" + baseDir.getAbsolutePath());
            }
        }
        log.info("[MessageFileManager] 创建baseDir目录成功！ baseDir=" + baseDir.getAbsolutePath());
        // 2. 创建队列数据文件
        File queueDataFile = new File(getQueueDataPath(queueName));
        if (!queueDataFile.exists()) {
            boolean createOK = queueDataFile.createNewFile();
            if (!createOK) {
                log.error("创建目录失败: {}", queueDataFile.getAbsolutePath());
                throw new IOException("[MessageFileManager] 创建目录失败！ queueDataFile=" + queueDataFile.getAbsolutePath());
            }
        }
        log.info("[MessageFileManager] 创建Data文件成功！ queueDataFile=" + queueDataFile.getAbsolutePath());
        // 3. 创建队列统计文件
        File queueStatFile = new File(getQueueStatPath(queueName));
        if (!queueStatFile.exists()) {
            boolean createOK = queueStatFile.createNewFile();
            if (!createOK) {
                log.error("[MessageFileManager] 创建目录失败: {}", queueStatFile.getAbsolutePath());
                throw new IOException("[MessageFileManager] 创建目录失败！ queueStatFile=" + queueStatFile.getAbsolutePath());
            }
        }
        log.info("[MessageFileManager] 创建Stat文件成功！ queueStatFile=" + queueStatFile.getAbsolutePath());
        // 4.给消息统计文件设置初始值：0\t0
        log.info("[MessageFileManager] 正在设置Stat初始值......");
        Stat stat = new Stat();
        stat.totalCount = 0;
        stat.validCount = 0;
        writeStat(queueName, stat);
        log.info("[MessageFileManager] Stat初始值设置完毕！");
    }

    // 删除队列的目录和文件
    public void destroyQueueFiles(String queueName) throws IOException {
        // 1. 删除目录里的文件
        File queueDataFile = new File(getQueueDataPath(queueName));
        boolean deleteDataOk = queueDataFile.delete();
        File queueStatFile = new File(getQueueStatPath(queueName));
        boolean deleteStatOk = queueStatFile.delete();
        // 2. 删除目录
        File baseDir = new File(getQueueDir(queueName));
        boolean deleteDirOk = baseDir.delete();
        // 任意一个删除失败都算整体失败
        if (!deleteDataOk || !deleteStatOk || !deleteDirOk) {
            throw new IOException("[MessageFileManager] 删除队列目录和文件失败！ baseDir=" + baseDir.getAbsolutePath());
        }
    }

    // 检查队列目录和文件是否存在
    public boolean checkFilesExits(String queueName) {
        File queueDataFile = new File(getQueueDataPath(queueName));
        if (!queueDataFile.exists()) {
            return false;
        }
        File queueStatFile = new File(getQueueStatPath(queueName));
        if (!queueStatFile.exists()) {
            return false;
        }
        File queueDir = new File(getQueueDir(queueName));
        if (!queueDir.exists()) {
            return false;
        }
        return true;
    }

    // 发送消息：把一个新msg放到queue中
    public void sendMessage(Message message, MSGQueue queue) throws MqException, IOException {
        // 1. 检查写入文件是否存在
        if (!checkFilesExits(queue.getName())) {
            throw new MqException("[MessageFileManager] 队列对应的文件不存在！queueName=" + queue.getName());
        }
        // 2. 把msg序列化为字节数组
        byte[] messageBinary = BinaryTool.toBytes(message);
        synchronized (queue) {
            // 3. 获取当前队列数据文件的长度，计算出该Msg对象的offset
            File queueDataFile = new File(getQueueDataPath(queue.getName()));
            message.setOffsetBeg(queueDataFile.length() + 4);
            message.setOffsetEnd(queueDataFile.length() + 4 + messageBinary.length);
            // 4. msg写入到数据文件
            try (OutputStream outputStream = new FileOutputStream(queueDataFile, true)) {
                try (DataOutputStream dataOutputStream = new DataOutputStream(outputStream)) {
                    // 先写入msg长度信息
                    dataOutputStream.writeInt(messageBinary.length);
                    // 写入msg内容
                    dataOutputStream.write(messageBinary);
                }
            }
            // 5. 更新统计文件
            Stat stat = readStat(queue.getName());
            stat.totalCount += 1;
            stat.validCount += 1;
            writeStat(queue.getName(), stat);
        }
    }

    // 删除消息：逻辑删除——消息的isValid=0
    public void deleteMessage(MSGQueue queue, Message message) throws IOException, ClassNotFoundException {
        synchronized (queue) {
            try (RandomAccessFile randomAccessFile = new RandomAccessFile(getQueueDataPath(queue.getName()), "rw")) {
                // 1. 从文件中读取对应的Message数据
                byte[] msgFromQueue = new byte[(int) (message.getOffsetEnd() - message.getOffsetBeg())];
                randomAccessFile.seek(message.getOffsetBeg());
                randomAccessFile.read(msgFromQueue);
                // 2. 把当前读取的二进制数据转换回Message数据
                Message msgFromDisk = (Message) BinaryTool.fromBytes(msgFromQueue);
                // 3. 把isValid设置为无效
                msgFromDisk.setIsValid((byte) 0x0);
                // 4. 重新写入文件
                byte[] msgBinary = BinaryTool.toBytes(msgFromDisk);
                // 让文件光标回到原始位置
                randomAccessFile.seek(message.getOffsetBeg());
                randomAccessFile.write(msgBinary);
            }
            // 5. 更新统计文件
            Stat stat = readStat(queue.getName());
            if (stat.validCount > 0) {
                stat.validCount -= 1;
            }
            writeStat(queue.getName(), stat);
        }
    }

    // 读取所有消息内容到内存中 —— 放到一个链表里
    // 该方法在程序准备启动时调用
    public LinkedList<Message> loadAllMsgFromQueue(String queueName) throws IOException, MqException, ClassNotFoundException {
        LinkedList<Message> msgList = new LinkedList<>();
        try (InputStream inputStream = new FileInputStream(getQueueDataPath(queueName))) {
            try (DataInputStream dataInputStream = new DataInputStream(inputStream)) {
                // curOffset 记录当前文件光标
                long curOffset = 0;
                // 文件中包含很多消息，需要循环读取
                while (true) {
                    // 1. 读取当前消息长度
                    int curMsgLength = dataInputStream.readInt();
                    // 2. 按照这个长度，读取消息内容
                    byte[] curMsgFromQueue = new byte[curMsgLength];
                    int lengthFromCurMsg = dataInputStream.read(curMsgFromQueue);
                    // 判断
                    if (curMsgLength != lengthFromCurMsg) {
                        // 两个长度不匹配，说名文件格式错乱
                        throw new MqException("[MessageFileManager] 文件格式错误！queueName=" + queueName);
                    }
                    // 3. 把数据反序列化回Message对象
                    Message message = (Message) BinaryTool.fromBytes(curMsgFromQueue);
                    // 4. 判定该对象是否有效
                    if (message.getIsValid() != 0x1) {
                        // 无效数据 —— 直接跳过 —— 更新offset到下一个消息的位置
                        curOffset += (4 + curMsgLength);
                        continue;
                    }
                    // 5. 有效数据 —— 加入链表
                    message.setOffsetBeg(curOffset + 4);
                    message.setOffsetEnd(curOffset + 4 + curMsgLength);
                    curOffset += (4 + curMsgLength);
                    msgList.add(message);
                }
            } catch (EOFException e) {
                // 文件读取到末尾时。readInt回抛出异常，这里需要处理
                log.info("[MessageFileManager] Message 加载到内存中已完成！");
            }
        }
        return msgList;
    }

    // 检查当前是否针对该队列的消息数据文件进行GC
    public boolean checkGC(String queueName) {
        Stat stat = readStat(queueName);
        if (stat.totalCount > 2000 && (double)stat.validCount / (double)stat.totalCount < 0.5) {
            return true;
        }
        return false;
    }

    // 指定新的 queue 文件路径
    public String getNewQueueDataPath(String queueName) {
        return getQueueDir(queueName) + "/queue_data_new.txt";
    }

    // 执行GC
    public void gc(MSGQueue queue) throws MqException, IOException, ClassNotFoundException {
        synchronized (queue) {
            long gcBeg = System.currentTimeMillis();
            // 1. 创建新文件
            File newQueueDataFile = new File(getNewQueueDataPath(queue.getName()));
            // 判断
            if (newQueueDataFile.exists()) {
                // 正常情况下这个文件不应该存在，如果存在 —— 说明上次GC进行到一半失败
                throw new MqException("[MessageFileManager] GC 时发现新的队列文件 queue_data_new.txt 已存在！ queueName=" + queue.getName());
            }
            boolean fileResult = newQueueDataFile.createNewFile();
            if (!fileResult) {
                throw new MqException("[MessageFileManager] GC 时创建新文件 queue_data_new.txt 失败！ queueName=" + queue.getName());
            }
            // 2. 读取出有效数据
            LinkedList<Message> validMessages = loadAllMsgFromQueue(queue.getName());
            // 3. 把之前消息数据中的有效消息复制到新文件
            try (OutputStream outputStream = new FileOutputStream(newQueueDataFile)) {
                try (DataOutputStream dataOutputStream = new DataOutputStream(outputStream)) {
                    for (Message message : validMessages) {
                        byte[] binaryMessage = BinaryTool.toBytes(message);
                        dataOutputStream.writeInt(binaryMessage.length);
                        dataOutputStream.write(binaryMessage);
                    }
                }
            }
            // 4. 删除旧文件
            File oldQueueDataFile = new File(getQueueDataPath(queue.getName()));
            fileResult = oldQueueDataFile.delete();
            if (!fileResult) {
                throw new MqException("[MessageFileManager] GC 时删除旧的文件数据失败！ oldQueueDataFile=" + oldQueueDataFile.getAbsolutePath());
            }
            // 5. 新文件改名字
            fileResult = newQueueDataFile.renameTo(oldQueueDataFile);
            if (!fileResult) {
                throw new MqException("[MessageFileManager] GC 时临时文件重命名失败！ newQueueDataFile=" + newQueueDataFile.getAbsolutePath()
                        + ", oldQueueDataFile=" + oldQueueDataFile.getAbsolutePath());
            }
            // 5. 更新消息统计文件
            Stat stat = readStat(queue.getName());
            stat.totalCount = validMessages.size();
            stat.validCount = validMessages.size();
            writeStat(queue.getName(), stat);

            long gcEnd = System.currentTimeMillis();
            log.info("[MessageFileManager] GC 执行完毕！ queueName=" + queue.getName() + "本次 GC 耗时：" + (gcEnd - gcBeg) + "ms");
        }
    }
}
